#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <assert.h>
#include <poll.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <netinet/in.h>

#include "rlib.h"

#define DATASIZE 500
#define ACKLEN 8
#define DATALEN 12

int verify_cksum(rel_t *r, packet_t *pkt, size_t n);


struct reliable_state {
  rel_t *next;			/* Linked list for traversing all connections */
  rel_t **prev;

  conn_t *c;			/* This is the connection object */

	/* sequence/ack counters */
	int ackno;
	int seqno;

	/* packets to store in window */
	packet_t *sent_data;
	packet_t *sent_ack;
	packet_t *recv_data;

	/* waiting flags */
	int waiting_for_ack;
	int waiting_for_data;
	int waiting_to_output;
	int sent_eof;
	int read_eof;
};

rel_t *rel_list;





/* Creates a new reliable protocol session, returns NULL on failure.
 * Exactly one of c and ss should be NULL.  (ss is NULL when called
 * from rlib.c, while c is NULL when this function is called from
 * rel_demux.) */
rel_t *
rel_create (conn_t *c, const struct sockaddr_storage *ss,
	    const struct config_common *cc)
{
  rel_t *r;

  r = xmalloc (sizeof (*r));
  memset (r, 0, sizeof (*r));

  if (!c) {
    c = conn_create (r, ss);
    if (!c) {
      free (r);
      return NULL;
    }
  }

  r->c = c;
  r->next = rel_list;
  r->prev = &rel_list;
  if (rel_list)
    rel_list->prev = &r->next;
  rel_list = r;

	/* initialize sequence/ack counters */
	r->ackno = 1;
	r->seqno = 1;

	/* initialize flags */
	r->waiting_for_ack = -1;
	r->waiting_for_data = 0;
	r->waiting_to_output = 0;
	r->sent_eof = 0;
	r->read_eof = 0;

	/* initialize space for packets stored */
	r->sent_data = malloc(sizeof(packet_t));
	r->recv_data = malloc(sizeof(packet_t));
	r->sent_ack = malloc(sizeof(packet_t));

  return r;
}

void
rel_destroy (rel_t *r)
{
  if (r->next)
    r->next->prev = r->prev;
  *r->prev = r->next;
  conn_destroy (r->c);

	/* free space for packets stored */
	free(r->sent_data);
	free(r->recv_data);
	free(r->sent_ack);
}


/* This function only gets called when the process is running as a
 * server and must handle connections from multiple clients.  You have
 * to look up the rel_t structure based on the address in the
 * sockaddr_storage passed in.  If this is a new connection (sequence
 * number 1), you will need to allocate a new conn_t using rel_create
 * ().  (Pass rel_create NULL for the conn_t, so it will know to
 * allocate a new connection.)
 */
void
rel_demux (const struct config_common *cc,
	   const struct sockaddr_storage *ss,
	   packet_t *pkt, size_t len)
{
}

/* given a packet, verify its checksum. return 0 if checksum is correct, -1 on 
 * error */
int verify_cksum(rel_t *r, packet_t *pkt, size_t n) {
	uint16_t checksum = pkt->cksum;
	uint16_t len = ntohs(pkt->len);
  
	/* check the checksum */	
	pkt->cksum = 0;
	if(len > DATALEN + DATASIZE || checksum != cksum(pkt, len)) {
		/* error with verifying checksum */
		pkt->cksum = checksum; /* revert checksum value */
		return -1;
	}
	pkt->cksum = checksum; /* revert checksum value */
	return 0;
}

void
rel_recvpkt (rel_t *r, packet_t *pkt, size_t n)
{
	if(verify_cksum(r, pkt, n) < 0) {
		return;
	}

	uint16_t len = ntohs(pkt->len);
	uint32_t ackno = ntohl(pkt->ackno);

	if(len != ACKLEN) { /* is a data packet */
		uint32_t seqno = ntohl(pkt->seqno); /* Only valid if length > ACKLEN */
		if(seqno < r->ackno) { /* duplicate packet */
			return;
		}

		r->waiting_for_data = 0;
		r->ackno = seqno + 1;

		memcpy(r->recv_data, pkt, len); /* copy pkt into r->recv_data */
		r->waiting_to_output = 1;
		rel_output(r); /* send data to be outputted */
	} else {
		if(r->waiting_for_ack == -1) {
			/* duplicate ack */
			return;
		}

		if(ackno > r->waiting_for_ack) {
			/* no longer waiting for input, try reading again */
			r->waiting_for_ack = -1;
			rel_read(r);
		}

	}

}


void
rel_read (rel_t *s)
{	
	if(s->waiting_for_ack == -1 && s->sent_eof == 1 && s->read_eof == 1) {
		/* tear down connection */
		rel_destroy(s);
		return;
	}


	if(s->waiting_for_ack != -1 || s->sent_eof) {
		/* haven't received ack yet, don't read input yet */
		return;
	}

	char *buf = malloc(DATASIZE);
	int num_bytes = conn_input(s->c, buf, DATASIZE);
	
	if(num_bytes == -1) {
		/* EOF or error received */
		s->sent_eof = 1;
		num_bytes = 0;
	} else if(num_bytes == 0) {
		return;
	}

	/* now, build the packet to send */
	packet_t *pkt = malloc(sizeof(packet_t));
	pkt->cksum = 0;
	int len = DATALEN + num_bytes;
	pkt->len = htons(len);
	pkt->ackno = htonl(s->ackno);
	pkt->seqno = htonl(s->seqno);
	memcpy(pkt->data, buf, num_bytes);
	pkt->cksum = cksum(pkt, len);
	conn_sendpkt(s->c, pkt, len);

	/* adjust flags, store packet just sent (for possible resending) */
	free(s->sent_data);
	s->sent_data = pkt;
	s->waiting_for_ack = s->seqno;
	s->seqno += 1;
	free(buf);
}

void
rel_output (rel_t *r)
{
	if(r->waiting_to_output == 0) {
		/* no data to send */
		return;
	}

	packet_t *datapkt = r->recv_data;
	uint16_t pktlen = ntohs(datapkt->len);

	size_t len = conn_bufspace(r->c);
	if(len >= pktlen - DATALEN) {
		/* create ack packet to send */
		struct ack_packet *ackp = malloc(sizeof(struct ack_packet));
		ackp->cksum = 0;
		ackp->len = htons(ACKLEN);
		ackp->ackno = htonl(r->ackno);
		ackp->cksum = cksum(ackp, ACKLEN);
		conn_sendpkt(r->c, (packet_t *) ackp, ACKLEN);

		/* update flags */
		free(r->sent_ack);
		r->sent_ack = (packet_t *) ackp;
		r->waiting_for_data = 1;

		if(pktlen == DATALEN) {
			r->read_eof = 1; /* EOF read */
			if(r->waiting_for_ack == -1 && r->sent_eof == 1 && r->read_eof == 1) {
				/* tear down connection */
				rel_destroy(r);
				return;
			}
		}
		
		conn_output(r->c, datapkt->data, pktlen - DATALEN);
		r->waiting_to_output = 0;
	}
}

void
rel_timer ()
{
  /* Retransmit any packets that need to be retransmitted */
	rel_t *r = rel_list;

	if(r->waiting_for_ack != -1) {
		/* try to resend data */
		conn_sendpkt(r->c, r->sent_data, ntohs(r->sent_data->len));
	}

	if(r->waiting_for_data == 1) {
		/* try to resend ack */
		conn_sendpkt(r->c, r->sent_ack, ACKLEN);
	}
}
